﻿using SyncCore.Sync.FrameSync;
using SyncCore.Sync.Helpers;
using SyncCore.Sync.Models;
using SyncDemo.Sync.Models;
using System.Net;
using System.Net.Sockets;

namespace SyncCore.Sync
{
    public enum ProgressState
    {
        Before, OnProgress, Completed,
    }

    // 创建主要是服务端的创建。
    public partial class Peer
    {
        #region fields

        private MessagePack _hostServerMessagePack;
        private AddressPortInfo _hostServerAddressPortInfo;
        private Socket _hostServerSocket;
        private byte[] _hostServerReceiveBuffer;
        private MemoryStream _hostServerReceiveMemoryStream;
        private byte[] _hostServerSendBuffer;
        private MemoryStream _hostServerSendMemoryStream;
        private HashSet<EndPoint> _connectedEndPoints;
        private FrameMessageCache _frameMessageCache;

        #endregion

        #region props

        public AddressPortInfo HostServerAddressPortInfo => _hostServerAddressPortInfo;

        public bool IsRoomLocked { get; private set; }

        #endregion

        #region methods

        ///<summary>锁定房间，锁定后，不会再接收加入房间的请求。</summary>
        public void LockRoom()
        {
            var connectedEndPoint = _connectedEndPoints;
            _frameMessageCache = new FrameMessageCache(connectedEndPoint);
            IsRoomLocked = true;
        }

        ///<summary>创建房间，创建后会接受其它人的加入房间的请求。</summary>
        public void CreateRoom()
        {
            _connectedEndPoints = new HashSet<EndPoint>();
            _hostServerReceiveBuffer = new byte[BufferSize];
            _hostServerReceiveMemoryStream = new MemoryStream(_hostServerReceiveBuffer);

            _hostServerSendBuffer = new byte[BufferSize];
            _hostServerSendMemoryStream = new MemoryStream(_hostServerSendBuffer);

            _hostServerSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
            _hostServerSocket.ReceiveBufferSize = BufferSize;
            _hostServerSocket.SendBufferSize = BufferSize;

            var ipAddress = IPAddress.Loopback;
            var port = 0;
            var ipEndPoint = new IPEndPoint(ipAddress, port);
            var endPoint = ipEndPoint as EndPoint;

            _hostServerSocket.Bind(endPoint);

            _hostServerAddressPortInfo = new AddressPortInfo(_hostServerSocket.LocalEndPoint);

            StartRoomService();

        }

        private void StartRoomService()
        {
            var thread = new Task(StartRoomServiceCore);
            thread.Start();
        }

        private void StartRoomServiceCore()
        {
            while (_cancellationTokenSource.IsCancellationRequested == false)
            {
                EndPoint remoteEndPoint = new IPEndPoint(IPAddress.Any, 0);
                _hostServerSocket.ReceiveFrom(_hostServerReceiveBuffer, 0, _hostServerReceiveBuffer.Length, SocketFlags.None, ref remoteEndPoint);
                _hostServerReceiveMemoryStream.Seek(0, SeekOrigin.Begin);

                var messagePack = _typeModel.DeserializeWithLengthPrefix(_hostServerReceiveMemoryStream, null, typeof(MessagePack), ProtoBuf.PrefixStyle.Base128, 1) as MessagePack;

                //Console.WriteLine($"Peer.StartRoomServiceCore: server received from {remoteEndPoint} content {messagePack.Type}");

                //// 纯聊天。
                //if (messagePack.Type == MessageType.Chat)
                //{
                //    var sendMessagePack = messagePack;
                //    _hostServerSendMemoryStream.Seek(0, SeekOrigin.Begin);
                //    _typeModel.SerializeWithLengthPrefix(_hostServerSendMemoryStream, messagePack, typeof(MessagePack), ProtoBuf.PrefixStyle.Base128, 1);

                //    foreach (var connectedEndPoint in _connectedEndPoints)
                //    {
                //        _hostServerSocket.SendTo(_hostServerSendBuffer, 0, _hostServerSendBuffer.Length, SocketFlags.None, connectedEndPoint); ;
                //    }
                //}
                // 加入房间。

                if (IsRoomLocked == false)
                {
                    ProcessUnlocked(remoteEndPoint, messagePack);
                }
                else
                {
                    ProcessLocked(remoteEndPoint, messagePack);
                }
            }
        }

        private void ProcessUnlocked(EndPoint remoteEndPoint, MessagePack? messagePack)
        {
            //Console.WriteLine($"Peer.ProcessUnlocked: 接收了 {remoteEndPoint} type {messagePack.Type} frameIndex {messagePack.FrameIndex}");
            if (messagePack.Type == MessageType.JoinRoom)
            {
                var sendMessagePack = MessagePackHelper.CreateSuccess();
                _hostServerSendMemoryStream.Seek(0, SeekOrigin.Begin);
                _typeModel.SerializeWithLengthPrefix(_hostServerSendMemoryStream, sendMessagePack, typeof(MessagePack), ProtoBuf.PrefixStyle.Base128, 1);

                _hostServerSocket.SendTo(_hostServerSendBuffer, 0, _hostServerSendBuffer.Length, SocketFlags.None, remoteEndPoint);

                _connectedEndPoints.Add(remoteEndPoint);
            }
            // 心跳。
            else if (messagePack.Type == MessageType.Heartbeat)
            {
                var sendMessagePack = MessagePackHelper.CreateSuccess();
                _hostServerSendMemoryStream.Seek(0, SeekOrigin.Begin);
                _typeModel.SerializeWithLengthPrefix(_hostServerSendMemoryStream, messagePack, typeof(MessagePack), ProtoBuf.PrefixStyle.Base128, 1);

                _hostServerSocket.SendTo(_hostServerSendBuffer, 0, _hostServerSendBuffer.Length, SocketFlags.None, remoteEndPoint);
            }
            // 客户端已准备好。
            else if (messagePack.Type == MessageType.ReadySync)
            {
                var sendMessagePack = MessagePackHelper.CreateWaiting();
                _hostServerSendMemoryStream.Seek(0, SeekOrigin.Begin);
                _typeModel.SerializeWithLengthPrefix(_hostServerSendMemoryStream, sendMessagePack, typeof(MessagePack), ProtoBuf.PrefixStyle.Base128, 1);

                _hostServerSocket.SendTo(_hostServerSendBuffer, 0, _hostServerSendBuffer.Length, SocketFlags.None, remoteEndPoint);
            }
            else
            {
                var sendMessagePack = MessagePackHelper.CreateFailed();
                _hostServerSendMemoryStream.Seek(0, SeekOrigin.Begin);
                _typeModel.SerializeWithLengthPrefix(_hostServerSendMemoryStream, sendMessagePack, typeof(MessagePack), ProtoBuf.PrefixStyle.Base128, 1);

                _hostServerSocket.SendTo(_hostServerSendBuffer, 0, _hostServerSendBuffer.Length, SocketFlags.None, remoteEndPoint);
            }
        }

        private void ProcessLocked(EndPoint remoteEndPoint, MessagePack? messagePack)
        {
            //Console.WriteLine($"Peer.ProcessLocked: 接收了 {remoteEndPoint} type {messagePack.Type} frameIndex {messagePack.FrameIndex}");
            // 房间内通讯。
            if (_connectedEndPoints.Contains(remoteEndPoint))
            {
                if (messagePack.Type == MessageType.JoinRoom)
                {
                    var sendMessagePack = MessagePackHelper.CreateSuccess();
                    _hostServerSendMemoryStream.Seek(0, SeekOrigin.Begin);
                    _typeModel.SerializeWithLengthPrefix(_hostServerSendMemoryStream, sendMessagePack, typeof(MessagePack), ProtoBuf.PrefixStyle.Base128, 1);

                    _hostServerSocket.SendTo(_hostServerSendBuffer, 0, _hostServerSendBuffer.Length, SocketFlags.None, remoteEndPoint);

                    _connectedEndPoints.Add(remoteEndPoint);
                }
                else if (messagePack.Type == MessageType.Heartbeat)
                {
                    var sendMessagePack = MessagePackHelper.CreateSuccess();
                    _hostServerSendMemoryStream.Seek(0, SeekOrigin.Begin);
                    _typeModel.SerializeWithLengthPrefix(_hostServerSendMemoryStream, sendMessagePack, typeof(MessagePack), ProtoBuf.PrefixStyle.Base128, 1);
                    _hostServerSocket.SendTo(_hostServerSendBuffer, 0, _hostServerSendBuffer.Length, SocketFlags.None, remoteEndPoint);
                }
                else if (messagePack.Type == MessageType.ReadySync)
                {
                    _frameMessageCache.CollectReady(remoteEndPoint);

                    if (_frameMessageCache.IsAllReady)
                    {
                        var sendMessagePack = MessagePackHelper.CreateSuccess();
                        _hostServerSendMemoryStream.Seek(0, SeekOrigin.Begin);
                        _typeModel.SerializeWithLengthPrefix(_hostServerSendMemoryStream, sendMessagePack, typeof(MessagePack), ProtoBuf.PrefixStyle.Base128, 1);
                        //_hostServerSocket.SendTo(_hostServerSendBuffer, 0, _hostServerSendBuffer.Length, SocketFlags.None, remoteEndPoint);
                        foreach (var connectedEndPoint in _connectedEndPoints)
                        {
                            _hostServerSocket.SendTo(_hostServerSendBuffer, 0, _hostServerSendBuffer.Length, SocketFlags.None, connectedEndPoint);
                        }
                    }
                    else
                    {
                        var sendMessagePack = MessagePackHelper.CreateWaiting();
                        _hostServerSendMemoryStream.Seek(0, SeekOrigin.Begin);
                        _typeModel.SerializeWithLengthPrefix(_hostServerSendMemoryStream, sendMessagePack, typeof(MessagePack), ProtoBuf.PrefixStyle.Base128, 1);
                        _hostServerSocket.SendTo(_hostServerSendBuffer, 0, _hostServerSendBuffer.Length, SocketFlags.None, remoteEndPoint);
                    }
                }
                // 返回同步RPC。
                else if (messagePack.Type == MessageType.RPC)
                {
                    //Console.WriteLine($"Peer.ProcessLocked: 接收了 {remoteEndPoint} RPC {messagePack.FrameIndex}");
                    _frameMessageCache.Collect(remoteEndPoint, messagePack);

                    if (_frameMessageCache.IsAllCollected)
                    {
                        var sendMessagePack = _frameMessageCache.CreateAssembledMessagePack();

                        _hostServerSendMemoryStream.Seek(0, SeekOrigin.Begin);
                        _typeModel.SerializeWithLengthPrefix(_hostServerSendMemoryStream, sendMessagePack, typeof(MessagePack), ProtoBuf.PrefixStyle.Base128, 1);

                        foreach (var connectedEndPoint in _connectedEndPoints)
                        {
                            _hostServerSocket.SendTo(_hostServerSendBuffer, 0, _hostServerSendBuffer.Length, SocketFlags.None, connectedEndPoint);
                        }

                        //Console.WriteLine($"Peer.ProcessLocked: 服务端发送了 RPC {sendMessagePack.FrameIndex}");
                        _frameMessageCache.SetNextFrameIndex();
                    }

                }
                else
                {
                    var sendMessagePack = MessagePackHelper.CreateFailed();
                    _hostServerSendMemoryStream.Seek(0, SeekOrigin.Begin);
                    _typeModel.SerializeWithLengthPrefix(_hostServerSendMemoryStream, messagePack, typeof(MessagePack), ProtoBuf.PrefixStyle.Base128, 1);
                    _hostServerSocket.SendTo(_hostServerSendBuffer, 0, _hostServerSendBuffer.Length, SocketFlags.None, remoteEndPoint);
                }
            }
            // 当遇到不在房间里的连接时直接返回失败。
            else
            {
                var sendMessagePack = MessagePackHelper.CreateFailed();
                _hostServerSendMemoryStream.Seek(0, SeekOrigin.Begin);
                _typeModel.SerializeWithLengthPrefix(_hostServerSendMemoryStream, sendMessagePack, typeof(MessagePack), ProtoBuf.PrefixStyle.Base128, 1);

                _hostServerSocket.SendTo(_hostServerSendBuffer, 0, _hostServerSendBuffer.Length, SocketFlags.None, remoteEndPoint);
            }
        }

        #endregion

    }

    // 主要是客户端的创建。 
    public partial class Peer
    {
        #region events

        ///<summary>同步包已接受。</summary>
        public event EventHandler<MessagePack> OnSyncPackReceived;

        ///<summary>同步帧失败。</summary>
        public event EventHandler OnSyscFrameError;

        #endregion

        #region fields

        private Socket _clientSocket;
        private EndPoint _remoteServerHostEndPoint;
        private byte[] _clientReceiveBuffer;
        private Stream _clientReceiveMemoryStream;
        private byte[] _clientSendBuffer;
        private Stream _clientSendMemoryStream;

        private ProgressState _isJoinedRoom;
        private ProgressState _isReadyAndWaitForStartSyncFrame;
        private ProgressState _isRunningSyncFrame;
        private MessagePack _clientSendSyncMessagePack;

        #endregion

        #region props

        public int FrameIndex { get; private set; }

        public AddressPortInfo ClientAddressPortInfo { get; private set; }
        public string ClientAddressPortInfoString { get; private set; }

        #endregion

        #region methods

        ///<summary>作为客户端加入房间。</summary>
        public void JoinRoom(AddressPortInfo addressPortInfo)
        {
            _clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
            _clientSocket.SendTimeout = 500;
            //_clientSocket.ReceiveTimeout = 500;
            _clientReceiveBuffer = new byte[BufferSize];
            _clientReceiveMemoryStream = new MemoryStream(_clientReceiveBuffer);

            _clientSendBuffer = new byte[BufferSize];
            _clientSendMemoryStream = new MemoryStream(_clientSendBuffer);

            var remoteServerHostEndPoint = new IPEndPoint(IPAddress.Parse(addressPortInfo.Address), addressPortInfo.Port);
            _remoteServerHostEndPoint = remoteServerHostEndPoint;

            _clientSocket.Connect(_remoteServerHostEndPoint);
            var localEndPoint = _clientSocket.LocalEndPoint;

            ClientAddressPortInfo = new AddressPortInfo(localEndPoint);
            ClientAddressPortInfoString = ClientAddressPortInfo.ToString();

            var thread2 = new Task(ReceiveMessagePackLoop);
            thread2.Start();

            var thread3 = new Task(SendJoinRoomMessage);
            thread3.Start();
        }

        private void SendJoinRoomMessage()
        {
            _isJoinedRoom = ProgressState.OnProgress;

            var joinMessagePack = new MessagePack()
            {
                Type = MessageType.JoinRoom
            };


            while (_isJoinedRoom == ProgressState.OnProgress)
            {
                SendMessagePack(joinMessagePack);
                Thread.Sleep(1000);
            }
        }

        public void SetReadyAndStartFrameIndex0()
        {
            var thread1 = new Task(SetReadyAndStartFrameIndex0Core);
            thread1.Start();
        }

        private void ReceiveMessagePackLoop()
        {
            while (_cancellationTokenSource.IsCancellationRequested == false)
            {
                //Console.WriteLine($"Peer.ReceiveMessagePackLoop: {ClientAddressPortInfo}");
                try
                {
                    //Console.WriteLine($"Peer.SendMessagePack: end   send from {ClientAddressPortInfo} cehcked {cehcked.Type} frameIndex {cehcked.FrameIndex}");
                    _clientReceiveMemoryStream.Seek(0, SeekOrigin.Begin);
                    _clientSocket.ReceiveFrom(_clientReceiveBuffer, 0, _clientReceiveBuffer.Length, SocketFlags.None, ref _remoteServerHostEndPoint);
                    var resultMessagePack = _typeModel.DeserializeWithLengthPrefix(_clientReceiveMemoryStream, null, typeof(MessagePack), ProtoBuf.PrefixStyle.Base128, 1) as MessagePack;
                    OnMessageReceived(resultMessagePack);
                }
                catch (Exception ex)
                {

                }
            }
        }

        private void SetReadyAndStartFrameIndex0Core()
        {
            _isReadyAndWaitForStartSyncFrame = ProgressState.OnProgress;
            //Console.WriteLine($"Peer.SetReadyAndStartFrameIndex0Core: {ClientAddressPortInfo} start ready");
            while (_isReadyAndWaitForStartSyncFrame == ProgressState.OnProgress)
            {
                SetReady();
                Thread.Sleep(1000);
            }

            //Console.WriteLine($"Peer.SetReadyAndStartFrameIndex0Core: 完成了准备 {ClientAddressPortInfo} start ready");
            StartSyncLoop(isCreateOtherThread: false);
        }

        public void SetReady()
        {
            var readyMessagePack = new MessagePack()
            {
                Type = MessageType.ReadySync,
            };
            //Console.WriteLine($"Peer.SetReady: begin send message pack");
            SendMessagePack(readyMessagePack);
            //Console.WriteLine($"Peer.SetReady: end   send message pack");

        }

        private void OnMessageReceived(MessagePack receivedMessagesPack)
        {
            Console.WriteLine($"Peer.OnMessageReceived: {ClientAddressPortInfo} {receivedMessagesPack.Type} {receivedMessagesPack.FrameIndex}");
            if (_isReadyAndWaitForStartSyncFrame == ProgressState.OnProgress)
            {
                if (receivedMessagesPack.Type == MessageType.Success)
                {
                    _isReadyAndWaitForStartSyncFrame = ProgressState.Completed;
                }
            }
            else if (_isRunningSyncFrame == ProgressState.OnProgress)
            {
                if (receivedMessagesPack.Type == MessageType.RPC)
                {
                    if (FrameIndex == receivedMessagesPack.FrameIndex)
                    {
                        FrameIndex++;
                    }
                    else
                    {
                        //Console.WriteLine($"Peer.StartSyncLoop: 帧丢失 目标帧 {FrameIndex} 同步帧 {receivedMessagesPack.FrameIndex}。");
                    }
                }
            }
            else if (_isJoinedRoom == ProgressState.OnProgress)
            {
                if (receivedMessagesPack.Type == MessageType.Success)
                {
                    _isJoinedRoom = ProgressState.Completed;
                }
            }
            else
            {

            }
        }


        public void SetClientSendSyncMessagePack(IEnumerable<CommandMessagePack> commandMessagePacks)
        {
            if (commandMessagePacks == null)
            {
                _clientSendSyncMessagePack = new MessagePack()
                {
                    Type = MessageType.RPC,
                    FrameIndex = FrameIndex,
                };
            }
            else
            {
                _clientSendSyncMessagePack = new MessagePack()
                {
                    Type = MessageType.RPC,
                    FrameIndex = FrameIndex,
                    Commands = commandMessagePacks.ToArray()
                };
            }
        }

        public void StartSyncLoop(bool isCreateOtherThread = false)
        {
            _isRunningSyncFrame = ProgressState.OnProgress;
            if (isCreateOtherThread)
            {
                var thread = new Task(StartSyncLoopCore);
                thread.Start();
            }
            else
            {
                StartSyncLoopCore();
            }

        }

        private MessagePack GetClientSendSyncMessagePack()
        {
            if (_clientSendSyncMessagePack == null) SetClientSendSyncMessagePack(null);
            _clientSendSyncMessagePack.FrameIndex = FrameIndex;
            return _clientSendSyncMessagePack;
        }

        private void StartSyncLoopCore()
        {
            while (_cancellationTokenSource.IsCancellationRequested == false)
            {
                //if (_clientSendSyncMessagePack == null) SetClientSendSyncMessagePack(null);
                var sendMessagePack = GetClientSendSyncMessagePack();

                SendMessagePack(_clientSendSyncMessagePack);

                Thread.Sleep(100);
            }
        }

        public void SendMessagePack(MessagePack messagePack)
        {
            try
            {
                //Console.WriteLine($"Peer.SendMessagePack: beigin send from {ClientAddressPortInfo} {messagePack.Type} frameIndex {messagePack.FrameIndex}");
                _clientSendMemoryStream.Seek(0, SeekOrigin.Begin);
                _typeModel.SerializeWithLengthPrefix(_clientSendMemoryStream, messagePack, typeof(MessagePack), ProtoBuf.PrefixStyle.Base128, 1);
                _clientSocket.SendTo(_clientSendBuffer, 0, _clientSendBuffer.Length, SocketFlags.None, _remoteServerHostEndPoint);

            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }

        }

        #endregion
    }
}
